/*******************************************************************************
* Copyright (c) 2008, Jeferson Jose de Miranda                                 *
* UDESC - Universidade do Estado de Santa Catarina                             *
* All rights reserved.                                                         *
*                                                                              *
* Redistribution and use in source and binary forms, with or without           *
* modification, are permitted provided that the following conditions are met:  *
*     * Redistributions of source code must retain the above copyright         *
*       notice, this list of conditions and the following disclaimer.          *
*     * Redistributions in binary form must reproduce the above copyright      *
*       notice, this list of conditions and the following disclaimer in the    *
*       documentation and/or other materials provided with the distribution.   *
*     * Neither the name of the Universidade do Estado de Santa Catarina nor   *
*       the names of its contributors may be used to endorse or promote        *
*       products derived from this software without specific prior written     *
*       permission.                                                            *
*                                                                              *
* THIS SOFTWARE IS PROVIDED BY JEFERSON JOSE DE MIRANDA "AS IS" AND ANY        *
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED    *
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE       *
* DISCLAIMED. IN NO EVENT SHALL JEFERSON JOSE DE MIRANDA BE LIABLE FOR ANY     *
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES   *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; *
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND  *
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF     *
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            *
*******************************************************************************/

/**
 * \file ImageDifference.hpp
 *
 * \brief Subtracts color images and place the result in a binary image.
 *
 * \author Jeferson Jose de Miranda
 */

#ifndef SKINCOLOR_HPP
#define SKINCOLOR_HPP

#include "RGBImage.hpp"
#include "GreyLevelImage.hpp"

namespace MoveIn
{
    /**
    * \class SkinColor
    *
    * \brief Segments the user by separating skin color from background.
    */
    class SkinColor
    {
        public:

        /**
        * \fn SkinColor::skinColor(RGBImage*,
        *                          GreyLevelImage*)
        *
        * \brief Taking the source color image as input, pixels in the
        * range of skin color (RGB) values will be turned to white in the
        * result image. Other pixels will be black.
        *
        * Uses a parametric approach in RGB color space.
        *
        * \param source
        * A colored image.
        *
        * \param result
        * The resulting binary image.
        */
        static void skinColor(RGBImage *source,
                              GreyLevelImage *result);

        /**
        * \fn SkinColor::normalizedSkinColor(RGBImage*,
        *                                    GreyLevelImage*)
        *
        * \brief Taking the source color image as input, pixels in the
        * range of skin color (normalized RGB) values will be turned to white
        * in the result image. Other pixels will be black.
        *
        * Uses a parametric approach in normalized RGB color space.
        *
        * \param source
        * A colored image.
        *
        * \param result
        * The resulting binary image.
        */
        static void normalizedSkinColor(RGBImage *source,
                                        GreyLevelImage *result);

        static void combinedSkinColor(RGBImage *source,
                                      GreyLevelImage * result);
    };

/*******************************************************************************
* MoveIn::SkinColor::skinColor                                                 *
*******************************************************************************/

    void SkinColor::skinColor(RGBImage *source, GreyLevelImage * result)
    {
        if(!Image::isSameSize(source, result))
        {
            throw new DifferentSize();
        }
        else
        {
            int h = source->getHeight();
            int w = source->getWidth();
            RGBColor c;
            unsigned int R, G, B;

            result->reset();

            for (int i=0; i<w; i++)
            {
                for (int j=0; j<h; j++)
                {
                    source->getPixel(i, j, &c);

                    R = c.getR();
                    G = c.getG();
                    B = c.getB();

                    if(// R > 95 //
                       R > 95 &&
                       // G > 40 //
                       G > 40 &&
                       // B > 20 //
                       B > 20 &&
                       // max(R, G, B) - min(R, G, B) > 15 //
                       MAXIMUM(MAXIMUM(R, G), B) -
                       MINIMUM(MINIMUM(R, G), B) > 15 &&
                       // | R - G | > 15 //
                       ABS(R - G) > 15 &&
                       // R > G //
                       R > G &&
                       // R > B //
                       R > B)
                    {
                        result->setPixel(i, j, 255);
                    }
                }
            }
        }
    }

/*******************************************************************************
* MoveIn::SkinColor::normalizedSkinColor                                       *
*******************************************************************************/

    void SkinColor::normalizedSkinColor(RGBImage *source,
                                        GreyLevelImage * result)
    {
        if(!Image::isSameSize(source, result))
        {
            throw new DifferentSize();
        }
        else
        {
            int h = source->getHeight();
            int w = source->getWidth();
            float r, g, b;
            RGBColor c;

            result->reset();

            for (int i=0; i<w; i++)
            {
                for (int j=0; j<h; j++)
                {
                    source->getPixel(i, j, &c);

                    r = (float) c.getR() / (c.getR() + c.getG() + c.getB());
                    g = (float) c.getG() / (c.getR() + c.getG() + c.getB());
                    b = (float) c.getB() / (c.getR() + c.getG() + c.getB());

                    if(// r / r = 1.185 //
                       (r / g) > 1.185 &&
                       // ((r * b) / (r + g + b) ^ 2) >  0.107 //
                       ((r * b) / ((r + g + b) * (r + g + b))) > 0.107 &&
                       // ((r * g) / (r + g + b) ^ 2) >  0.112 //
                       ((r * g) / ((r + g + b) * (r + g + b))) > 0.112)
                    {
                        result->setPixel(i, j, 255);
                    }
                }
            }
        }
    }

/*******************************************************************************
* MoveIn::GreyLevelImage::combinedSkinColor                                    *
*******************************************************************************/

    void SkinColor::combinedSkinColor(RGBImage *source,
                                      GreyLevelImage * result)
    {
        if(!Image::isSameSize(source, result))
        {
            throw new DifferentSize();
        }
        else
        {
            int h = source->getHeight();
            int w = source->getWidth();
            float r, g, b;
            RGBColor c;
            unsigned int R, G, B;

            result->reset();

            for (int i=0; i<w; i++)
            {
                for (int j=0; j<h; j++)
                {
                    source->getPixel(i, j, &c);

                    R = c.getR();
                    G = c.getG();
                    B = c.getB();

                    r = (float) c.getR() / (c.getR() + c.getG() + c.getB());
                    g = (float) c.getG() / (c.getR() + c.getG() + c.getB());
                    b = (float) c.getB() / (c.getR() + c.getG() + c.getB());

                    if(// Simple RGB //
                       (// R > 95 //
                        R > 95 &&
                        // G > 40 //
                        G > 40 &&
                        // B > 20 //
                        B > 20 &&
                        // max(R, G, B) - min(R, G, B) > 15 //
                        MAXIMUM(MAXIMUM(R, G), B) -
                        MINIMUM(MINIMUM(R, G), B) > 15 &&
                        // | R - G | > 15 //
                        ABS(R - G) > 15 &&
                        // R > G //
                        R > G &&
                        // R > B //
                        R > B) ||
                        // Normalized RGB //
                       (// r / r = 1.185 //
                        (r / g) > 1.185 &&
                        // ((r * b) / (r + g + b) ^ 2) >  0.107 //
                        ((r * b) / ((r + g + b) * (r + g + b))) > 0.107 &&
                        // ((r * g) / (r + g + b) ^ 2) >  0.112 //
                        ((r * g) / ((r + g + b) * (r + g + b))) > 0.112))
                    {
                        result->setPixel(i, j, 255);
                    }
                }
            }
        }
    }
}

#endif

